package com.googlecode.horncomb.web.spring.mvc;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.validation.BindException;
import org.springframework.validation.Errors;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException;

import com.googlecode.horncomb.web.extremetable.util.ExtremeTableUtil;
import com.googlecode.horncomb.web.spring.mvc.support.ServiceMethodNoFoundException;
import com.googlecode.horncomb.web.util.PageMessageFilter;

/**
 * @author Daniel
 */
@SuppressWarnings("unchecked")
public class FormServiceController<MainServiceType extends Object> extends
		FormEventController {
	protected MainServiceType formService = null;
	protected String showFormServiceMethod = "show";
	private Map<String, Method> serviceMethodMap = new HashMap<String, Method>();
	private Boolean paged = true;
	private Map<String, String> eventViewMap = null;

	protected FormServiceController() {
		super();
	}

	@Override
	protected void initApplicationContext() {
		super.initApplicationContext();
		// cache service method
		for (Method method : this.formService.getClass().getMethods()) {
			Class<?> returnType = method.getReturnType();
			if (!"wait".equals(method.getName())
					&& !"notifyAll".equals(method.getName())) {
				if (Map.class.equals(returnType)
						|| void.class.equals(returnType)) {
					if (method.getParameterTypes().length > 0
							&& this.getCommandClass().isAssignableFrom(
									method.getParameterTypes()[0])) {
						this.serviceMethodMap.put(method.getName(), method);
					}
				}
			}
		}
	}

	@Override
	protected Map<String, Object> referenceData(HttpServletRequest request,
			Object command, Errors errors) throws Exception {
		Method method = this.serviceMethodMap.get(this.showFormServiceMethod);
		if (method == null
				|| !Map.class.isAssignableFrom(method.getReturnType())) {
			throw new NoSuchRequestHandlingMethodException(
					this.showFormServiceMethod, getClass());
		}
		if (command instanceof ListForm) {
			ListForm listForm = (ListForm) command;
			listForm.getPagedSettings().clear();
			listForm.getPagedSettings().putAll(
					ExtremeTableUtil.resolvePagedSettings(request));
		}
		Object returnValue = this.invokeServiceMethod(method, command);
		return ((returnValue instanceof Map) ? (Map) returnValue : null);
	}

	protected Map<String, Object> listReference(HttpServletRequest request,
			Object command, Errors errors) throws Exception {
		if ((command instanceof ListForm) && paged) {
			ListForm listForm = (ListForm) command;
			listForm.getPagedSettings().clear();
			listForm.getPagedSettings().putAll(
					ExtremeTableUtil.resolvePagedSettings(request));
		}
		return (Map<String, Object>) this.invokeService(request, command);
	}

	@Override
	public ModelAndView defaultEventHandle(HttpServletRequest request,
			HttpServletResponse response, Object command, BindException errors)
			throws Exception {
		Object returnValue = this.invokeService(request, command);
		ModelAndView mav = this.getEventView(request, command, errors,
				((returnValue instanceof Map) ? (Map) returnValue : null));
		if (returnValue != null) {
			if (returnValue != null && returnValue instanceof Map) {
				mav.addAllObjects((Map) returnValue);
			} else {
				this.logger.debug("ignore return value");
			}
		}
		return mav;
	}

	// protected ModelAndView

	protected ModelAndView getEventView(HttpServletRequest request,
			Object command, BindException errors, Map controlModel)
			throws Exception {
		ModelAndView mav = null;
		if (this.eventViewMap != null) {
			String viewName = this.eventViewMap.get(this.resolveRequestEvent(
					request, command));
			if (StringUtils.hasText(viewName)) {
				mav = resolveView(viewName, controlModel);
			}
		}
		if (mav == null) {
			mav = this.getSuccessModeAndView(request, command, errors,
					controlModel);
		}
		if (mav == null) {
			throw new ServletException("can not build ModelAndView");
		}
		return mav;
	};

	protected void addPageReturnMessage(HttpSession session, String message) {
		ArrayList<String> pageReturnMessage = (ArrayList<String>) session
				.getAttribute(PageMessageFilter.DEFAULT_MESSAGES_KEY);
		if (pageReturnMessage == null) {
			pageReturnMessage = new ArrayList();
			session.setAttribute(PageMessageFilter.DEFAULT_MESSAGES_KEY,
					pageReturnMessage);
		}
		pageReturnMessage.add(message);
	}

	private Object invokeService(HttpServletRequest request, Object command)
			throws Exception {
		String methodName = this.resolveRequestEvent(request, command);
		Method method = this.getServiceMethod(methodName);
		if (method == null) {
			throw new NoSuchRequestHandlingMethodException(methodName,
					getClass());
		}
		return invokeServiceMethod(method, new Object[] { command });
	}

	protected Object invokeServiceMethod(Method method, Object command)
			throws Exception {
		return method.invoke(this.formService, new Object[] { command });
	}

	/**
	 * @param event
	 * @return not null.
	 * @throws ServiceMethodNoFoundException
	 */
	protected Method getServiceMethod(String event)
			throws ServiceMethodNoFoundException {
		String methodName = this.resolveEventServiceMethodName(StringUtils
				.hasText(event) ? event : this.defaultEventName);
		Method serviceMethod = serviceMethodMap.get(methodName);
		if (serviceMethod == null) {
			throw new ServiceMethodNoFoundException(event, this.formService
					.getClass());
		}
		return serviceMethod;
	}

	protected String resolveEventServiceMethodName(String serviceMethodName) {
		return serviceMethodName;
	}

	public void setFormService(MainServiceType mainService) {
		Assert.notNull(mainService);
		this.formService = mainService;
	}

	public Boolean isPaged() {
		return paged;
	}

	public void setPaged(Boolean paged) {
		this.paged = paged;
	}

	/**
	 * @param eventViewMap
	 *            the eventViewMap to set
	 */
	public void setEventViewMap(Map<String, String> eventViewMap) {
		this.eventViewMap = eventViewMap;
	}

	/**
	 * @param showFormServiceMethod
	 *            the showFormServiceMethod to set
	 */
	public void setShowFormServiceMethod(String showFormServiceMethod) {
		this.showFormServiceMethod = showFormServiceMethod;
	}
}
